///////////////////////////////////////////////////////////////////////////////////
//
// Writer  : Dong-moon Lim, Dept. of Messenger, SK Communications, Korea
//
// Email   : dmlim@nate.com
//
// Created : 2009-03-31 09:48:00
//
// Description
//		Co-Operation Ticket Module
//
///////////////////////////////////////////////////////////////////////////////////

#include <ctype.h>
#include <time.h>
#include <algorithm>
#include "coticket.h"
#include "capicom.h"
#include "nmalloc.h"

#if _MSC_VER > 1300 
#pragma warning( disable : 4996 )
#endif 

using namespace std;
using namespace NCAPICOM;

#define _IS_SPECIAL(x) ( (x) == '&' || (x) == '=' || (x) == '%' )
#define _IS_HEX(x) ( '0' <= (x) && (x) <= '9' || 'a' <= (x) && (x) <= 'f' || 'A' <= (x) && (x) <= 'F' )

namespace COTICKET
{
	class EncodeLen
	{
	private:
		size_t size;
	
	public:
		EncodeLen ( ) : size (0)
		{
		}

		void operator ( ) (char e)
		{
			size += _IS_SPECIAL(e) ? 3:1;
		}

		operator size_t ( )
		{
			return size;
		}
	};

	class KeyValLen
	{
	private:
		size_t size;

	public:
		KeyValLen ( ) : size ( 0 )
		{
		}

		void operator ( ) ( pair<string, string> e )
		{
			size += for_each(e.first.begin(), e.first.end(), EncodeLen());
			size += for_each(e.second.begin(), e.second.end(), EncodeLen());
			size += 2;
		}

		operator size_t ( )
		{
			return size;
		}
	};

	class KeyValMerge
	{
	private:
		char* buf;
		char* offset;
		static char hex[];

	public:
		KeyValMerge(size_t len)
		{
			offset = buf = (char*)calloc(len, 1);
		}

		void add_string(string& s)
		{
			for ( string::iterator pch = s.begin() ; pch != s.end() ; pch++ )
			{
				if ( _IS_SPECIAL(*pch) )
				{
					*offset++ = '%';
					*offset++ = hex[*pch >> 4 & 0x0f];
					*offset++ = hex[*pch & 0x0f];
				}
				else
				{
					*offset++ = *pch;
				}
			}
		}

		void operator ( ) ( pair<string, string> e )
		{
			if ( buf != offset ) *offset++ = '&';
			add_string(e.first);
			*offset++ = '=';
			add_string(e.second);
		}

		operator char* ( )
		{
			return buf;
		}
	};

	char KeyValMerge::hex[] = "0123456789abcdef";

	CoTicket::CoTicket(const char* charset)
	{
		if ( charset )
		{
			m_charset = _strdup(charset);
		}
		else
		{
			m_charset = 0;
		}

		m_ticket = 0;
	}

	CoTicket::~CoTicket()
	{
		if ( m_charset ) free(m_charset);
		if ( m_ticket ) free(m_ticket);
	}

	map<string, string>& CoTicket::getProperties()
	{
		return m_properties;
	}

	void CoTicket::setProperties(map<string, string>& properties)
	{
		if ( !m_properties.empty() ) m_properties.clear();
		m_properties.insert(properties.begin(), properties.end());
	}

	const char* CoTicket::getProperty(const char* key)
	{
		map<string, string>::const_iterator it = m_properties.find(key);
		if ( it == m_properties.end() )
		{
			return 0;
		}
		return it->second.c_str();
	}

	void CoTicket::setProperty(const char* key, const char* value)
	{
		m_properties.insert(pair<string, string>(key, value));
	}

	typedef enum
	{
		NORMAL,
		PERCENT,
		FIRSTHEX,
	} URL_DECODE_STATUS;

	char CoTicketHex2BinCh(char ch)
	{
		if ( '0' <= ch && ch <= '9' )
			return ch - '0';
		else if ( 'a' <= ch && ch <= 'f' )
			return ch - 'a' + 0x0a;
		else
			return ch - 'A' + 0x0a;
	}

	char CoTicketHex2BinStr(char* start)
	{
		return CoTicketHex2BinCh(*start) << 4 | CoTicketHex2BinCh(*(start+1));
	}

	char* CoTicketDecodeParam(char* start, char* end)
	{
		char* target = start;
		char* offset = start;
		URL_DECODE_STATUS status = NORMAL;

		for ( ; offset < end ; offset++ )
		{
			switch ( status )
			{
			case NORMAL:
				if ( *offset == '%' )
				{
					status = PERCENT;
				}
				else
				{
					*target++ = *offset;
				}
				break;
			case PERCENT:
				if ( _IS_HEX(*offset) )
				{
					status = FIRSTHEX;
				}
				else
				{
					*target++ = *(offset-1);
					*target++ = *offset;
					status = NORMAL;
				}
				break;
			case FIRSTHEX:
				if ( _IS_HEX(*offset) )
				{
					*target++ = CoTicketHex2BinStr(offset-1);
				}
				else
				{
					*target++ = *(offset-2);
					*target++ = *(offset-1);
					*target++ = *offset;
				}
				status = NORMAL;
				break;
			}
		}

		*target = 0;
		return start;
	}

	void OnCoTicketParsed(char* key_start, char* key_end, char* val_start, char* val_end, void* param)
	{
		CoTicket* pCoTicket = (CoTicket*)param;
		pCoTicket->setProperty(
			CoTicketDecodeParam(key_start, key_end), CoTicketDecodeParam(val_start, val_end));
	}

	bool CoTicket::setTicket(const char* password, const char* ticket, bool isCheckTimeout)
	{
		time_t curtime = time(NULL);

		if ( !password || !*password )
		{
			SET_ERROR_MESSAGE(_T("CoTicket::setTicket() : password is NULL or null string"));
			return false;
		}

		if ( !ticket || !*ticket )
		{
			SET_ERROR_MESSAGE(_T("CoTicket::setTicket() : ticket is NULL or null string"));
			return false;
		}

		EncryptedData encData(0, m_charset);
		encData.SetSecret(password);
		encData.SetAlgorithm(CALG_AES_128);
		if ( !encData.Decrypt(ticket) )
		{
			SET_ERROR_MESSAGE(_T("CoTicket::setTicket() : Can't decrypt ticket"));
			return false;
		}

		if ( m_ticket ) 
		{
			free(m_ticket);
			m_ticket = 0;
		}
		m_ticket = _strdup(encData.GetContentA());

		//printf("%s\n", m_ticket);

		m_properties.clear();
		KeyValueParserUtil(m_ticket, '&', '=', OnCoTicketParsed, this);
		const char* s_utcexpire = getProperty("utcexpire");

		if ( isCheckTimeout == true )
		{
			if ( !s_utcexpire || !*s_utcexpire )
			{
				SET_ERROR_MESSAGE(_T("CoTicket::setTicket() : utcexpire value is null"));
				return false;
			}

			for ( const char* offset = s_utcexpire ; *offset ; offset++ )
			{
				if ( !isdigit(*offset) ) 
				{
					SET_ERROR_MESSAGE(_T("CoTicket::setValue() : invalid utcexpire value"));
					return false;
				}
			}

			time_t utcexpire = (time_t)strtoul(s_utcexpire, 0, 10);

			if ( curtime > utcexpire )
			{
				SET_ERROR_MESSAGE(_T("CoTicket:setValue() : ticket is expired"));
				return false;
			}
		}

		return true;
	}

	const char* CoTicket::getTicket(const char* password, unsigned long timeoutSeconds, bool isURL)
	{
		time_t curtime = time(NULL);
		char strtime[20];
		size_t length = 0;
		char* merged = 0;
		EncryptedData encData(0, m_charset);
		NBLOB* ticketBlob = 0;

		if ( !password )
		{
			SET_ERROR_MESSAGE(_T("Coticket:getTicket() : password is NULL"));
			return 0;
		}

		sprintf(strtime, "%lu", (unsigned long)curtime + timeoutSeconds);
		setProperty("utcexpire", strtime);

		length = for_each(m_properties.begin(), m_properties.end(), KeyValLen());
		merged = for_each(m_properties.begin(), m_properties.end(), KeyValMerge(length));

		//printf("%s\n", merged);

		encData.SetSecret(password);
		encData.SetContent(merged);
		encData.SetAlgorithm(CALG_AES_128);

		ticketBlob = encData.Encrypt(NCRYPT_BLOB_BASE64, isURL ? TRUE:FALSE);
		if ( !ticketBlob )
		{
			SET_ERROR_MESSAGE(_T("Can't encrypt coticket"));
			goto cleanup;
		}

		if ( m_ticket ) 
		{
			free(m_ticket);
			m_ticket = 0;
		}
		m_ticket = (char*)ticketBlob->pData;

	cleanup:

		if ( merged ) free(merged);
		if ( ticketBlob ) free(ticketBlob);

		return m_ticket;
	}
}
